#ifndef cArduino_CPP
#define cArduino_CPP 1

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <errno.h>

/*
Arduino Serial Port defaults:
9600 bps
SERIAL_8N1

Arduino Serial Port Comunication

Serial Conection Based On:

	arduino playground
	http://playground.arduino.cc/Interfacing/LinuxTTY

        Gary Frerking   gary@frerking.org
        http://www.tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html

	chrisheydrick
	http://chrisheydrick.com/2012/06/17/how-to-read-serial-data-from-an-arduino-in-linux-with-c-part-3/

*/

using namespace std;

enum ArduinoBaundRate{

  B300bps  = B300,
  B600bps  = B600,
  B1200bps = B1200,
  B2400bps = B2400,
  B4800bps = B4800,
  B9600bps = B9600,
//B14400bps= B14400,
  B19200bps=B19200,
//B28800bps=B28800
//B38400bps=38400
//B57600bps=57600
  B115200bps=115200
};


class cArduino
{
	//Arduino port name (example /dev/ttyACM0)
	char *portname=0;

	//serial port FileDescriptor*/
	int fd=0;

	//error description (OK=no errror)
	string errorString ="OK";


	//open serial port
	bool openDevice(ArduinoBaundRate baundRate)
	{
		// Open the file descriptor in non-blocking mode
		fd = open(portname, O_RDWR | O_NOCTTY);

		if(fd==0){
		errorString ="can't open device";
		errorString+=strerror(errno);
		return false;
		}

                // Wait for the Arduino to reset
                usleep(1000*1000);

                // Flush anything already in the serial buffer
                //tcflush(fd, TCIFLUSH);

		return true;
	}


	//set Serial Port Atributes
	//method 1 - shell comand
        bool setAttr_1(ArduinoBaundRate baundRate)
	{
		//command + arguments
       	        char buff[255];

               	sprintf(buff,
			"stty -F %s cs8 %d ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts",
			portname,
			baundRate);
		//--

		//execute comand
		FILE* pipe = popen(buff, "r");
		        if (!pipe)
                	{
		                errorString = "cArduino.cpp::setAttr_1 error: can't execute comand";
                                return false;
                	}

		string result = "";//comand output
                	while(!feof(pipe)) {
				char buffer[128];
		                if(fgets(buffer, 128, pipe) != NULL)
                                result += buffer;
                	}
			int stat=pclose(pipe);

		int return_code=WEXITSTATUS(stat);//exit code

			if(return_code!=0)
			{
				errorString = "cArduino.cpp::setAttr_1 error: execute error";
				return false;
			}
		return true;
	}

	//set Serial Port Atributes
	//method 2 - tcsetattr function
	bool setAttr_2(ArduinoBaundRate baundRate)
	{
		if(fd==0)
		{
		errorString = "cArduino.cpp::setAttr_2 fd=0";
		return false;
		}

		// Set up the control structure
		struct termios toptions;

		// Get currently set options for the tty
		tcgetattr(fd, &toptions);

		// Set custom options

		// 9600 baud
		cfsetispeed(&toptions, baundRate);
		cfsetospeed(&toptions, baundRate);

		// 8 bits, no parity, no stop bits
		toptions.c_cflag &= ~PARENB;
		toptions.c_cflag &= ~CSTOPB;
		toptions.c_cflag &= ~CSIZE;
		toptions.c_cflag |= CS8;

		// no hardware flow control
		toptions.c_cflag &= ~CRTSCTS;

		// enable receiver, ignore status lines
		toptions.c_cflag |= CREAD | CLOCAL;

		// disable input/output flow control, disable restart chars
		toptions.c_iflag &= ~(IXON | IXOFF | IXANY);

		// disable canonical input, disable echo,
		// disable visually erase chars,
		// disable terminal-generated signals
		toptions.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);

		// disable output processing
		toptions.c_oflag &= ~OPOST;

		// wait for 24 characters to come in before read returns
		toptions.c_cc[VMIN] = 1;
		// no minimum time to wait before read returns
		toptions.c_cc[VTIME] = 0;

		// commit the options
		if(tcsetattr(fd, TCSANOW, &toptions)!=0)
		{
			errorString ="cArduino::setAttr error";
	                errorString+=strerror(errno);
			return false;
		}

		return true;
	}

public:

	//returns error description
	string getError()
	{
		return errorString;
	}

	//can user Read and Write to/from device
        bool canReadWrite()
        {
		if(portname==0)
			return false;

                if(access(portname,W_OK | R_OK)!=0){
			errorString = "can't access (read/write) device";
                        return false;
		}

                return true;
        }

	//full open procedure for arduino
	//set or find arduino
	//veryfy read/write access
	//set Serial port attributes
	//open port
	bool Open(ArduinoBaundRate baundRate=B9600bps,char *devName=0)
	{
		if(devName==0)
		{
			if(FindArduino()==0){
			errorString = "can't find Arduino";
			return false;
			}
		}else{
			portname=devName;
		}

		if(!canReadWrite())   		return false;
		if(!setAttr_1(baundRate))	return false;
		if(!openDevice(baundRate))	return false;

		return true;
	}

	//Find Arduino device
	char *FindArduino()
	{
		char dir[] = "/dev/serial/by-id/";

		DIR *d=opendir(dir);

		if(d == NULL) //Couldn't open directory
		return 0;

		struct dirent *de=NULL;
		while(de = readdir(d))
		{
			if(strstr(de->d_name,"arduino")!=0)
			{
				char s[PATH_MAX+1];
				sprintf(s,"%s%s",dir,de->d_name);

				char buf[1024];
				int len;

				if ((len = readlink(s, buf, sizeof(buf)-1)) != -1)
				buf[len] = '\0';

				portname=new char[PATH_MAX+1];
				realpath(s,portname);

				closedir(d);
				return portname;
			}
		}
		closedir(d);
		return 0;
	}

	//is Arduino Serial Post Open
	bool isOpen()
	{
		if(fd==0) return false;
		return true;
	}

	//Read from Arduino (no timeout)
	string Read()
	{
		char buf[255];

		int res = read(fd,buf,255);
		buf[res]=0;

		string ret(buf);
		return ret;
	}

	/*read form arduino (witch timeout)
	 *ret - responce
	 *timeOut_MiliSeconds - timeout for read (1000 mili sec = 1 sec)
	 *print_error - print errors to stderr?
	 */
	bool Read(string &ret,int timeOut_MiliSeconds=500,bool print_error=false)
	{
		char buff[100];
		int len = 100;

		fd_set set;
		FD_ZERO(&set); /* clear the set */
		FD_SET(fd, &set); /* add our file descriptor to the set */

		struct timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = timeOut_MiliSeconds;

		int rv = select(fd + 1, &set, NULL, NULL, &timeout);

		if(rv == -1){
			if(print_error)
			perror("arduino select"); /* an error accured */
		}
		else
		{
			if(rv == 0)
			{
				if( print_error )
				fputs("arduino timeout!\n", stderr); /* a timeout occured */
			}else{
				ret=Read();
				return true; /* there was data to read */
			}
		}

		ret="";
		return false;
	}

	//Flush port
	void Flush()
	{
		if(fd!=0)
		tcflush(fd, TCIFLUSH);
	}

	//write to Arduinio
	void Write(string text) {
		::write(        fd,(char*)text.c_str(),(size_t)text.length() );
	}

};

#endif
